#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/fcntl.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define LISTEN_QUEUE_LENGTH 8
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 256

ssize_t readline(int fd, char *buf, size_t max_len)
{
    char c = 0;
    size_t count;
    for (count = 0; count < max_len; count += 1)
    {
        int n = read(fd, &c, 1);
        
        if (n == 0) // Done reading
        {
            break;
        }
        if (n == -1) // Error
        {
            if (errno == EINTR) continue;

            perror("read");
            return -1;
        }
        
        if (c == 4) // EOF, the client has disconnected.
        {
            return -1;
        }


        buf[count] = c;

        if (c == '\n') // Done reading one line.
        {
            return count + 1;
        }
        
    }
    
    return count; 
    
}

ssize_t writeline(int fd, const char *buf, size_t length)
{
    size_t written = 0;
    int remaining = 0;

    for (remaining = length; remaining > 0; remaining -= written)
    {
        written = write(fd, buf, remaining);
        if (written <= 0) break;
    }
    
    return length - remaining;
}


int last_killed = -1;           // Hack to for disconnecting users
int num_fds = 0;                // Number of clients connected.
int all_fds[MAX_CLIENTS];       // Stores active clients fd's
struct sockaddr_in server_addr; // Holds the server listener port's struct.

int create_socket(int port)
{
    int listenfd;
   
    memset(&server_addr, 0, sizeof (server_addr));
    
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    
    if (listenfd == -1) 
    {
        perror("socket");
        return -1;
    }
    int retval = bind(listenfd, (struct sockaddr *) &server_addr, sizeof (server_addr));
    if (retval) 
    {
        perror("bind");
        
        return -1;
    }
    retval = listen(listenfd, LISTEN_QUEUE_LENGTH);
    if (retval) 
    {
        perror("listen");
        
        return -1;
    }
    return listenfd; 
}



// Remove the given fd from our list.
void delete_fd(int fd)
{
    // Debugging
    // printf("deleting %d\n", fd);
    // printf("Before: ");
    // for(int i = 0; i < num_fds; i++)
    // {
        // printf(" %d,", all_fds[i]);
    // }
    // printf("\b\n");
	
    for(int i = 0; i < num_fds; i++)
    {
        if(all_fds[i] == fd)
        {
            for(int j = i; j < num_fds-1; j++)
            {
                all_fds[j] = all_fds[j+1];
            }
            num_fds--;
            break;
        }
    }
	
    // Debugging
    // printf("After: ");
    // for(int i = 0; i < num_fds; i++)
    // {
        // printf(" %d,", all_fds[i]);
    // }
    // printf("\b\n");
}

// Finds the maximum file descriptor
int findmaxfd(int first)
{
    int max = first;
    for(int i = 0; i < num_fds; i++)
    {
        if(all_fds[i] > max)
        {
            max = all_fds[i];
        }
    }
    return max; 
}

int main(int argc, char *argv[])
{
    int listenfd;
    int acceptfd;
    struct sockaddr_in* client_addr = NULL;
    socklen_t client_addr_len;
    char buffer[BUFFER_SIZE];
    
    if (argc != 2) 
    {
        fprintf(stderr, "Usage: %s <port>\n", argv[0]);
        return -1;
    }
	
    int port = atoi(argv[1]);
	
    listenfd = create_socket(port); 
    if (listenfd < 0) 
    {
        fprintf(stderr, "Failed to create socket.\n");
		exit(1);
    }
    
    fd_set rfds; 
    while(1)
    {
        FD_ZERO(&rfds);
        FD_SET(listenfd, &rfds);
        //printf("listenfd is %d\n", listenfd);
        for(int i = 0; i < num_fds; i++)
        {
            FD_SET(all_fds[i], &rfds);
        }

        int fdmax = findmaxfd(listenfd);
        //printf("max fd %d\n", fdmax);
        //printf("Before select\n");
        int num_found = select(fdmax+1, &rfds, NULL, NULL, NULL);
        if (num_found == 0) 
		{
            printf("Blocking select call returned zero matches (impossible?)\n");
            continue;
        }
        if(FD_ISSET(listenfd, &rfds))
        {
            //printf("new connection!\n");

			// Sometimes when a user disconnects, this signals a
			// "new connection found" for some reason. The below
			// check is a work-around.
            if(last_killed != -1)
            {
                //int read = readline(last_killed, buffer, BUFFER_SIZE);
                //printf("read %d from last_killed\n", read);
                close(last_killed);
                last_killed = -1;
                int retval = listen(listenfd, LISTEN_QUEUE_LENGTH);
                if (retval) 
                {
                    perror("listen");
                    return -1;
                }
                continue; 
            }
            else
            {
                //printf("nothing killed \n");
            }
            client_addr = malloc(sizeof(struct sockaddr_in));
            acceptfd = accept(listenfd, (struct sockaddr*)client_addr, &client_addr_len);
            //printf("connected!\n");
            if(acceptfd == -1)
            {
                fprintf(stderr, "Failed attempt to connect client\n");
            }
            else
            {
                all_fds[num_fds] = acceptfd;
                num_fds++;
                //printf("accepted client %d\n", acceptfd);
            }
        }
        else
        {
            //printf("message!\n");
            for(int i = 0; i < num_fds; i++)
            {
                int readfd = all_fds[i];
                int num_read;
                if(FD_ISSET(readfd, &rfds))
                {
                    //printf("message from %d\n", readfd);
                    num_read = readline(readfd, buffer, BUFFER_SIZE);
                    //printf("done readlining %d bytes from %d\n", num_read, readfd);
                    if(num_read <= 0)
                    {
                        //printf("client %d disconnected\n", readfd);
                        delete_fd(readfd);
                        close(readfd);
                        last_killed = readfd;
                        //printf("we killed %d \n", last_killed);
                        break; 
                    }
                  
                    for(int j = 0; j < num_fds; j++)
                    { 
                        if(all_fds[j] == readfd)
						{
						    // Do not echo to ourselves.
                            continue; 
                        }
                        ssize_t res = writeline(all_fds[j], buffer, num_read);
                        // if (res <= 0)
                        // {
                            // close(all_fds[j]);
                        // }
                    }

                }
            }
        }
    }
    return 0;
}

